/* eslint-disable no-console */
/* eslint-disable import/prefer-default-export */
import type { WebSocketConfig } from '../types';
import TioHandler from './TioHandler';

interface ITioConnection {
  ws: WebSocket;
}

export const wsInstance: ITioConnection = {
  ws: {} as WebSocket,
};

export class TioConnection {
  private config: WebSocketConfig;

  private handler: TioHandler = {} as TioHandler;

  wsInstance: WebSocket | null = null;

  private reconnectTimer?: number = 0;

  private heartbeatTimer?: number = 0;

  constructor(config: string | URL | WebSocketConfig, handler?: TioHandler) {
    if (typeof config === 'string' || config instanceof URL) {
      this.config = {
        heartbeatTimeout: 5000,
        reconnInterval: 3000,
        binaryType: 'arraybuffer',
        url: config,
      };
    } else {
      this.config = {
        heartbeatTimeout: 5000,
        reconnInterval: 3000,
        binaryType: 'arraybuffer',
        ...config,
      };
    }
    this.handler = handler || new TioHandler();
    this.connect();
  }

  private connect() {
    this.cleanup();

    try {
      wsInstance.ws = new WebSocket(this.config.url, this.config.protocols);
      this.wsInstance = wsInstance.ws;
      this.wsInstance.binaryType = this.config.binaryType!;

      this.setupEventHandlers();
    } catch (error) {
      console.error('WebSocket initialization failed:', error);
      this.scheduleReconnect();
    }
  }

  private setupEventHandlers() {
    if (!this.wsInstance) return;

    this.wsInstance.onopen = (event) => {
      this.handler.onOpen?.(event);
      this.startHeartbeat();
    };

    this.wsInstance.onmessage = (event) => {
      this.handler.onMessage?.(event);
    };

    this.wsInstance.onerror = (event) => {
      this.handler.onError?.(event);
      this.cleanup();
    };

    this.wsInstance.onclose = (event) => {
      this.handler.onClose?.(event);
      this.scheduleReconnect();
    };
  }

  private startHeartbeat() {
    const interval = (this.config.heartbeatTimeout ?? 5000) / 2;
    this.heartbeatTimer = setInterval(() => {
      this.handler.ping?.();
    }, interval);
  }

  private scheduleReconnect() {
    if (this.reconnectTimer) return;

    this.reconnectTimer = setTimeout(() => {
      this.reconnectTimer = undefined;
      this.connect();
    }, this.config.reconnInterval);
  }

  public send(data: string | ArrayBuffer | Blob) {
    if (this.wsInstance?.readyState === WebSocket.OPEN) {
      this.wsInstance.send(data);
    }
  }

  public close() {
    this.cleanup();
  }

  public reconnect() {
    this.cleanup();
    this.connect();
  }

  private cleanup() {
    this.wsInstance?.close();
    this.wsInstance = null;

    if (this.heartbeatTimer) {
      clearInterval(this.heartbeatTimer);
      this.heartbeatTimer = undefined;
    }

    if (this.reconnectTimer) {
      clearTimeout(this.reconnectTimer);
      this.reconnectTimer = undefined;
    }
  }
}
